package com.xdqzy.baseline.common.manager;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.xdqzy.baseline.common.annotation.Dict;
import com.xdqzy.baseline.common.converter.AbstractBizConverter;
import com.xdqzy.baseline.common.model.SimpleMark;
import com.xdqzy.baseline.common.model.SysMarkModel;
import com.xdqzy.baseline.common.service.SysMarkService;
import com.xdqzy.baseline.common.utils.ReflectUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

import javax.annotation.PostConstruct;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@Component
public class StaticDataManager {

    @Autowired(required = false)
    private List<AbstractBizConverter<?, ?>> converters;

    private static final PropertyDescriptor[] EMPTY_PROPERTY_DESCRIPTOR = new PropertyDescriptor[0];

    private static final String KEY_SUFFIX = "_str";

    @Autowired
    private SysMarkService markService;

    private Map<String, LinkedHashMap<String, String>> markKeymap = new ConcurrentHashMap<>();

    private Map<String, List<SimpleMark>> markSimpleMap = new ConcurrentHashMap<>();

    private Map<String, SysMarkModel> markModelMap = new ConcurrentHashMap<>();

    private static Map<Class<?>, List<FieldContext>> typeFieldMap = Maps.newConcurrentMap();

    @PostConstruct

    public void init() {
        loadMarkData();
    }

    public void loadMarkData() {
        Wrapper<SysMarkModel> sortWrapper = Wrappers.<SysMarkModel>query().orderByAsc("mark_type", "mark_sort");
        List<SysMarkModel> markModels = markService.list(sortWrapper);
        markKeymap.clear();
        markModelMap.clear();
        markSimpleMap.clear();
        markModels.forEach(mark -> {
            markKeymap.computeIfAbsent(mark.getMarkType().toUpperCase(),
                    k -> new LinkedHashMap<>()).put(mark.getMarkKey(), mark.getMarkValue());
            markModelMap.put(mark.buildKey(), mark);
            markSimpleMap.computeIfAbsent(mark.getMarkType().toUpperCase(),
                    k -> new ArrayList<>()).add(new SimpleMark(mark));
        });
        log.info("数据字典加载完成,共加载:{}条", markModels.size());
    }

    public Map<String, String> getTypeMap(String type) {
        return markKeymap.getOrDefault(type.toUpperCase(), new LinkedHashMap<>());
    }

    public List<SimpleMark> getSimpleMark(String type) {
        return markSimpleMap.getOrDefault(type.toUpperCase(), Collections.emptyList());
    }

    public List<String> getMarkTypes() {
        return Lists.newArrayList(markKeymap.keySet());
    }

    /**
     * @param <T>
     * @param objs 需要转化的对象集合
     * @param keys 需要翻译的key名称，翻译后的值存在key_str属性中，如user_state翻译后的属性在user_state_str中
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     */
    public <T> void convertObjectMark(List<?> objs, Class<T> type,
                                      String... keys) {
        if (CollectionUtils.isEmpty(objs) || ArrayUtils.isEmpty(keys)) {
            return;
        }
        for (int i = 0; i < objs.size(); i++) {
            Object obj = objs.get(i);
            if (obj == null) {
                continue;
            }
            try {
                for (String key : keys) {
                    PropertyDescriptor[] propertyDescriptors = exactForAttrDescriptor(type, key);
                    if (propertyDescriptors.length != 2) {
                        continue;
                    }
                    convertMarkValue(propertyDescriptors[0], propertyDescriptors[1], obj, key);
                }
            } catch (Exception e) {
                log.error("exact mark attr error", e);
                return;
            }
        }

    }

    /**
     * 转换指定属性的字典值
     *
     * @param objs   对象集合
     * @param type   对象类型
     * @param keyMap 转换map , key为对象属性名称,value为字典类型
     * @param <T>
     */
    public <T> void convertObjectMark(List<?> objs, Class<T> type,
                                      Map<String, String> keyMap) {
        if (CollectionUtils.isEmpty(objs) || CollectionUtils.isEmpty(keyMap)) {
            return;
        }
        for (int i = 0; i < objs.size(); i++) {
            Object obj = objs.get(i);
            if (obj == null) {
                continue;
            }
            try {
                for (Map.Entry<String, String> entry : keyMap.entrySet()) {
                    PropertyDescriptor[] propertyDescriptors = exactForAttrDescriptor(type, entry.getKey());
                    if (propertyDescriptors.length != 2) {
                        continue;
                    }
                    PropertyDescriptor descriptor = propertyDescriptors[0];
                    PropertyDescriptor markDescriptor = propertyDescriptors[1];
                    convertMarkValue(descriptor, markDescriptor, obj, entry.getValue());
                }
            } catch (Exception e) {
                log.error("exact mark attr error", e);
                return;
            }
        }

    }

    /**
     * 通过注解注入字典字段
     *
     * @param list
     * @param type
     * @param <T>
     */
    public <T> void convertObjectList(List<T> list, Class<T> type) {
        if (CollectionUtils.isEmpty(list) || type == null) {
            return;
        }
        List<FieldContext> fieldContexts = getFieldContexts(type);
        if (CollectionUtils.isEmpty(fieldContexts)) {
            return;
        }
        list.stream().filter(Objects::nonNull).forEach(o -> fieldContexts.forEach(f -> {
                    Object sourceKey = ReflectionUtils.invokeMethod(f.getSourceDescriptor().getReadMethod(), o);
                    if (sourceKey != null) {
                        String[] keys = sourceKey.toString().split(",");
                        String value = Stream.of(keys).map(k -> Optional.ofNullable(markModelMap.get(f.dictType + "@" + k)).orElse(new SysMarkModel(k, k)).getMarkValue()).collect(Collectors.joining(","));
                        ReflectionUtils.invokeMethod(f.getStrDescriptor().getWriteMethod(), o, value);
                    }
                })
        );

    }

    public <T> void convertObject(T obj, Class<T> type) {

        convertObjectList(Collections.singletonList(obj), type);

    }

    private <T> PropertyDescriptor[] exactForAttrDescriptor(Class<T> type, String key) {
        PropertyDescriptor descriptor = BeanUtils
                .getPropertyDescriptor(type, key);
        if (descriptor == null
                || descriptor.getReadMethod() == null) {
            log.warn("can't find property:{}"
                    + " or readMethod in {}", key, type);
            return EMPTY_PROPERTY_DESCRIPTOR;
        }
        // 获取key对应的mark属性描述符，如user_state的mark属性是mark_state_str
        String convertKey = key + KEY_SUFFIX;
        PropertyDescriptor markDescriptor = BeanUtils
                .getPropertyDescriptor(type, convertKey);
        if (markDescriptor == null
                || markDescriptor.getWriteMethod() == null) {
            log.warn("can't find property:{} or wirteMethod in {}", convertKey, type);
            return EMPTY_PROPERTY_DESCRIPTOR;
        }
        return new PropertyDescriptor[]{descriptor, markDescriptor};
    }

    private void convertMarkValue(PropertyDescriptor reader, PropertyDescriptor writer, Object obj, String key)
            throws InvocationTargetException, IllegalAccessException {
        if (StringUtils.isBlank(key)) {
            log.warn("convert type key is empty");
            return;
        }
        Object value = reader.getReadMethod().invoke(obj);
        // 需要翻译的属性值
        if (value != null) {
            LinkedHashMap<String, String> markTypeMap = markKeymap
                    .get(key.toUpperCase());// 某类属性的mark
            String valueKey = value.toString();// 转成int类型的属性值
            if (markTypeMap != null
                    && markTypeMap.containsKey(valueKey)) {
                writer.getWriteMethod().invoke(obj,
                        markTypeMap.get(valueKey));
            } else {
                writer.getWriteMethod().invoke(obj, value.toString());
            }
        }
    }

    /**
     * 获取类型中带字典注解的字段
     *
     * @param type
     * @param <T>
     * @return
     */
    private static <T> List<FieldContext> getFieldContexts(Class<T> type) {
        return typeFieldMap.computeIfAbsent(type, t -> {
            List<FieldContext> list = new ArrayList<>();
            ReflectionUtils.doWithFields(type, f -> {
                PropertyDescriptor source = BeanUtils.getPropertyDescriptor(t, f.getName());
                if (source != null && source.getReadMethod() != null) {
                    Dict dict = f.getAnnotation(Dict.class);
                    PropertyDescriptor target = BeanUtils.getPropertyDescriptor(t, f.getName() + KEY_SUFFIX);
                    if (target != null && target.getWriteMethod() != null) {
                        list.add(new FieldContext(dict.value(), source, target));
                    }
                }
            }, f -> f.getAnnotation(Dict.class) != null);
            return list;
        });
    }

    public Map<String, SysMarkModel> getMarkModelMap() {
        return markModelMap;
    }

    @Data
    private static class FieldContext {
        /**
         * 字典类型
         */
        private String dictType;
        /**
         * 原始字段
         */
        private PropertyDescriptor sourceDescriptor;
        /**
         * 描述字段
         */
        private PropertyDescriptor strDescriptor;

        public FieldContext(String dictType, PropertyDescriptor sourceDescriptor, PropertyDescriptor strDescriptor) {
            this.dictType = dictType;
            this.sourceDescriptor = sourceDescriptor;
            this.strDescriptor = strDescriptor;
        }
    }

    /**
     * 批量注入关联字段
     * @param models
     */
    public void combineModels(List models) {
        if (CollectionUtils.isEmpty(models)) {
            return;
        }
        models.stream().filter(Objects::nonNull).findAny().ifPresent(c -> ReflectUtil.linearConvert(converters, models, c.getClass()));
    }

    /**
     * 注入关联字段
     * @param model
     * @param <T>
     */
    public <T> void combineModel(T model){
        if(model==null){
            return;
        }
        ReflectUtil.linearConvert(converters, Collections.singletonList(model), (Class<? super T>) model.getClass());
    }

}
